package cn.kawa.user.service.impl;


import cn.kawa.basic.exception.BusinessException;
import cn.kawa.basic.jwt.JwtUtils;
import cn.kawa.basic.jwt.LoginData;
import cn.kawa.basic.jwt.RsaUtils;
import cn.kawa.basic.service.impl.BaseServiceImpl;
import cn.kawa.basic.util.HttpUtil;
import cn.kawa.basic.util.JsonResult;
import cn.kawa.basic.util.Md5Utils;
import cn.kawa.basic.util.StrUtils;
import cn.kawa.system.domain.Menu;
import cn.kawa.user.constant.WxConstants;
import cn.kawa.user.domain.LoginInfo;
import cn.kawa.user.domain.User;
import cn.kawa.user.domain.WxUser;
import cn.kawa.user.dto.BinderDto;
import cn.kawa.user.dto.LoginDto;
import cn.kawa.user.mapper.LoginInfoMapper;
import cn.kawa.user.mapper.UserMapper;
import cn.kawa.user.mapper.WxUserMapper;
import cn.kawa.user.service.ILoginInfoService;
import cn.kawa.user.service.IUserService;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
@Service
public class LoginInfoServiceImpl extends BaseServiceImpl<LoginInfo> implements ILoginInfoService {
    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;
    @Autowired
    private WxUserMapper wxUserMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private IUserService userService;


    /**
     * 此方法用于实现用户输入的登录
     * @param loginDto
     * @return Map<String, Object>  token，loginInfo
     */
    @Override
    public Map<String, Object> accountLogin(LoginDto loginDto) {
        //取出loginDto的全部值
        String account = loginDto.getAccount();
        Integer loginType = loginDto.getLoginType();
        String password = loginDto.getPassword();
        //1.校验 - 空值校验
        if(StringUtils.isEmpty(account) || StringUtils.isEmpty(loginType) || StringUtils.isEmpty(password) ){
            throw new BusinessException("数据不能为空~");
        }
        //2.校验 - 账号校验 //account可以是 ：账号-电话-邮箱
        LoginInfo loginInfo= loginInfoMapper.loadByAccount(loginDto);
        if(loginInfo==null){//
            throw new BusinessException("账号错误~");
        }
        //3.校验 - 密码校验
        String inputMdPwd = Md5Utils.encrypByMd5(password + loginInfo.getSalt());
        if(!inputMdPwd.equals(loginInfo.getPassword())){
            throw new BusinessException("账号或密码错误~");
        }
        //4.校验 - 是否启用
        if(!loginInfo.getDisable()){
            throw new BusinessException("账户失效，请联系管理员~");
        }



        /*//4.成功后去除密码，盐值，返回obj，以及token[已弃用]
        loginInfo.setPassword(null);
        loginInfo.setSalt(null);
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token",token);
        map.put("loginInfo",loginInfo);*/
        /**
         * 创建并调用方法进行登录成功的操作
         * @param loginInfo
         */
        HashMap<String, Object> map =loginSuccessJwtHandler(loginInfo);

        return map;
    }

    /**
     *  1.JSON WEB TOKEN它是一个协议，用于安全在服务器、浏览器端传输数据
     *  2.三部分组成 ： 头部 ，载荷，签名
     *  3.优点：
     *  可以跨语言支持
     *  可以自定载荷
     *  可以签名放篡改
     *  可以解决无状态token方案在服务器端redis存储数据 过多的问题
     *  4.登录成功后将logininfo和权限信息加密保存在jwt载荷返回给浏览器端
     *  浏览器端每次发送请求携带jwt后端interceptor解析，获取登录和权限信息
     * @param loginInfo
     * @return
     */
    private HashMap<String, Object> loginSuccessJwtHandler(LoginInfo loginInfo) {
        LoginData loginData = new LoginData();
        loginData.setLoginInfo(loginInfo);
        List<String> permissions =null;
        List<Menu> menus=null;
        loginInfo.setPassword(null);
        loginInfo.setSalt(null);
        HashMap<String, Object> map = new HashMap<>();
        try {
            if(loginInfo.getType()==0){//0===管理员
                //1.获取所有权限permission
                permissions =loginInfoMapper.LoadPermissionsByLoginInfoId(loginInfo.getId());
                //2.获取所有菜单 menu
                menus=loginInfoMapper.LoadMenusByLoginInfoId(loginInfo.getId());
                loginData.setPermissions(permissions);
                loginData.setMenus(menus);
            }
            //3.加密
            PrivateKey privateKey = RsaUtils.getPrivateKey(RsaUtils.class.getClassLoader().getResource("auth_rsa.pri").getFile());
            //PrivateKey privateKey2 = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource("auth_rsa.pri").getFile());


            String jwtToken = JwtUtils.generateTokenExpireInMinutes(loginData, privateKey, 300);
            //4.装到map返回
            map.put("token",jwtToken);
            map.put("loginInfo",loginInfo);
            map.put("permissions",permissions);
            map.put("menus",menus);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 此方法用于实现用户的wx扫码登录
     * @param code：用于向第三方发送第二次请求
     * @return true:JsonResult - map【 token，loginInfo】 或者 false:【openid,access_token】
     */
    @Override
    public JsonResult wechatLogin(String code) {
        //1.根据code发送请求获取token和openid
        String url = WxConstants.GET_ACK_URL.replace("APPID", WxConstants.APPID)
                .replace("SECRET", WxConstants.SECRET)
                .replace("CODE", code);
        /**
         * 前端用户点击“确认登录”后通过回调的callback.html 钩子函数直接走到这里/login/wechat/
         * 1.此处为向微信端发送的第二次请求， （用户不确认的话，就没有code）
         * 作用： 通过前端用户点击【确认】后转到回调页面（callback.html）传回来的code，利用HttpUtil发送请求
         * 获取access_token 和 openid ，并通过openid检索wxUser 是否已有值
         * 如果有，绑定过就返回ture并登录，利用map返回登录态的token以及loginInfo
         * 没有，就要发请求将access_token 和 openid放进map，返回false给前端跳转/binder.html
         *
         */
        String jsonStr = HttpUtil.httpGet(url);//发送请求，jsonStr接收返回openid 和access_token

        //System.out.println(jsonStr);
        //2.将响应的json字符串转成json对象
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        String access_token = jsonObject.getString("access_token");
        String openid = jsonObject.getString("openid");

        //3.通过openid去wxuser中查询 ， 获取wxUser对象,
        WxUser wxUser = wxUserMapper.loadByOpenid(openid);
        if(wxUser!=null && wxUser.getUser_id()!=null){
            //4.wxUser有值 ， 通过 wxUser 查询 logininfo  免密登录 cv
            LoginInfo loginInfo =loginInfoMapper.loadByUserid(wxUser.getUser_id());

            HashMap<String, Object> map =loginSuccessJwtHandler(loginInfo);
            /*loginInfo.setPassword(null);
            loginInfo.setSalt(null);
            String token = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(token,loginInfo,29, TimeUnit.MINUTES);
            HashMap<String, Object> map = new HashMap<>();
            map.put("token",token);
            map.put("loginInfo",loginInfo);*/
            return JsonResult.me().setResultObj(map);

        }else {
            //5.没有值，返回路径给前端跳转:  进行用户绑定
            HashMap<String, Object> map = new HashMap<>();
            map.put("access_token",access_token);
            map.put("openid",openid);
            return JsonResult.me().setSuccess(false).setResultObj(map);
        }



    }

    /**前端用户点击“确认绑定”后通过binder.html走到这里，路径/verifyCode/binderSmsCode
     * 【账号登录接口】：用于user端的微信绑定，
     * 此处向微信发送第三次请求 ， 携带参数 openid 和access_token ,phone,verifyCode
     * 参数传递过程：后端wechatLogin方法-> callback.html(此处后台发送请求二，获取两个参数并传出来)->
     * binder.html ->wechatBinder(此方法)
     *
     * @param binderDto：phone,verifyCode,accessToken,openid
     * @return JsonResult
     */
    @Override
    public JsonResult wechatBinder(BinderDto binderDto) {
        //System.out.println(binderDto);
        String phone = binderDto.getPhone();
        String verifyCode = binderDto.getVerifyCode();
        String accessToken = binderDto.getAccessToken();
        String openid = binderDto.getOpenid();

        //1.校验 - 空值校验 BinderDto(phone=13330964748, verifyCode=yITgf2, accessToken=59_KvS_R...., openid=oa9wA08ay8GCKdbqXFmblICIZhhk)
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(verifyCode)){
            throw new BusinessException("数据不能为空~~");
        }

        //2.校验 - 验证码是否过期
        Object redisCode = redisTemplate.opsForValue().get("binder:" + phone);
        if(redisCode==null){
            throw new BusinessException("验证码过期~~");
        }
        //3.校验 - 验证码是否正确 redisCode 为 obj，需要toString [binder:1311 ,code:123123123]
        if(!redisCode.toString().split(":")[0].equalsIgnoreCase(verifyCode.trim())){
            throw new BusinessException("验证码错误，请重新输入");
        }

        /**
         * 绑定user 和 wxUser
         */
        //4.获取wxUser - 发送第三个请求 - 要从url 发送请求，获取参数 再最后 json字符串
        String url = WxConstants.GET_USER_URL.replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openid);
        /**
         *
         */
        String jsonStr = HttpUtil.httpGet(url);//发送第三次请求
        //JSONObject jsonObject = JSONObject.parseObject(jsonStr);//封装到方法
        /**
         * 调用对象转换方法，新增User同时新增LoginInfo的话，可不可以直接调userService已改造的方法哇
         */
        WxUser wxUser =jsonStr2wxUser(jsonStr);
        //5.通过phone查询user
        User user = userMapper.loadByPhone(phone);
        //5.1 如果没有，就需要同时创建 user 和 logininfo对象
        if(user==null){
            user=phone2user(phone);
            user.setHeadImg(wxUser.getHeadimgurl());
            userService.add(user);
        }

        //6.将user对象的id设置到WxUser中
        wxUser.setUser_id(user.getId());
        //7.将wxUser添加到数据库
        wxUserMapper.save(wxUser);
        LoginInfo loginInfo =loginInfoMapper.loadById(user.getLogininfo_id());
        HashMap<String, Object> map =loginSuccessJwtHandler(loginInfo);
        /*//8.做免密登录
        loginInfo.setPassword(null);
        loginInfo.setSalt(null);
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token,loginInfo,29, TimeUnit.MINUTES);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token",token);
        map.put("loginInfo",loginInfo);*/
        return JsonResult.me().setResultObj(map);
    }
    /**
     * 退出接口：用于user端的退出登录
     * @param token
     * @return JsonResult
     */
    @Override
    public void logout(String token) {
        redisTemplate.delete(token);
    }

    private User phone2user(String phone) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);

        //随机纯数字密码
        String random = StrUtils.getRandomString(6);
        /*String salt = StrUtils.getComplexRandomString(32);
        String md5 = Md5Utils.encrypByMd5(random + salt);
        user.setPassword(md5);*/
        user.setPassword(random);
        return user;
    }

    private WxUser jsonStr2wxUser(String jsonStr) {
        WxUser wxUser = new WxUser();
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        //System.out.println(jsonObject);
        //BeanUtils.copyProperties(jsonObject,wxUser);//不能用BeanUtils
        wxUser.setOpenid(jsonObject.getString("openid"));
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setAddress(jsonObject.getString("country")+jsonObject.getString("province")+jsonObject.getString("city"));
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxUser.setUnionid(jsonObject.getString("uniond"));
        return wxUser;
    }


    //1.loadAll();
    //2.loadById
    //3.save
    //4.remove
    //5.update
    //6.return new PageList<>(count,shops);
    //7.patchDel
}
